package com.ruoyi.common.websocket.server;

import java.lang.reflect.Field;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;

import javax.annotation.PostConstruct;
import javax.websocket.CloseReason;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.Threads;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.websocket.handler.WebsocketMsgHandler;
import com.ruoyi.common.websocket.listener.WsCloseListener;
import com.ruoyi.common.websocket.listener.WsOpenListener;

import lombok.extern.slf4j.Slf4j;

@Component
@ServerEndpoint("/websocket/{group}/{username}") // group pc电脑端 app 移动端 gateway 网关 edge 边缘端
@Slf4j
public class WebsocketServer {
	@Value("${server.servlet.context-path}")
	private String contextPath;
	@Value("${server.port}")
	private String port;

	@PostConstruct
	void init() {
		log.info("===================Websocket Server 初始化 ===================");
		ServerEndpoint serverEndpoint = WebsocketServer.class.getAnnotation(ServerEndpoint.class);
		String path = serverEndpoint.value();
		log.info("Websocket Server connect url = ws://127.0.0.1:" + port + contextPath + path);
	}

	@Scheduled(cron = "0/60 * * * * ?")
	public void checkUpdate() throws Exception {
		Iterator<String> it = WebSocketUtils.clients.keySet().iterator();
		while (it.hasNext()) {
			String username = it.next();
			Session session = WebSocketUtils.getSession(username);
			Map<String, Object> userProperties = session.getUserProperties();
			Long updateTime = (Long) userProperties.get("updateTime");
			long now = System.currentTimeMillis();
			if (updateTime != null) {
				long t = (now - updateTime.longValue()) / 1000L;
				if (t > 90) {
					log.warn("websocket client {} 超过 {} 秒心跳包,强制断开链接", username, t);
					session.close();
				}
			}
		}
	}

	private Object getFieldInstance(Object obj, String fieldPath) {
		String fields[] = fieldPath.split("#");
		for (String field : fields) {
			obj = getField(obj, obj.getClass(), field);
			if (obj == null) {
				return null;
			}
		}
		return obj;

	}

	private Object getField(Object obj, Class clazz, String fieldName) {
		for (; clazz != Object.class; clazz = clazz.getSuperclass()) {
			try {
				Field field = clazz.getDeclaredField(fieldName);
				field.setAccessible(true);
				return field.get(obj);
			} catch (Exception e) {

			}
		}
		return null;
	}

	@OnOpen
	public void openSession(Session session, @PathParam("group") String group, @PathParam("username") String username) {
		try {
			Map<String, Object> userProperties = session.getUserProperties();
			userProperties.put("group", group);
			String clientIp = getClientIp(session);
			userProperties.put("clientIp", clientIp);
			updateHeartbeat(session);

			// Map<String, List<String>> params = session.getRequestParameterMap();
			log.info(clientIp + " client " + username + " connect success");
			session.setMaxIdleTimeout(30 * 60 * 1000L); // -1永不超时
			// 如果不满足维一性,则关闭重所有连接,让客户端自动重连以满足维一性
			if (WebSocketUtils.clients.containsKey(username)) {
				WebSocketUtils.clients.get(username).close();
				session.close();
				log.warn("{} 存在重复连接", username);
				return;
				// throw new RuntimeException(username + "重复连接");
			}
			WebSocketUtils.clients.put(username, session);
			log.info("Websocket Client size = {}", WebSocketUtils.clients.size());
			String beanKey = group + WsOpenListener.class.getSimpleName();
			if (SpringUtils.containsBean(beanKey)) {
				onOpenTrigger(session, username, beanKey);
			}else {
				String prefix = username.split("-")[0];
				beanKey = prefix + WsOpenListener.class.getSimpleName();
				if (SpringUtils.containsBean(beanKey)) {
					onOpenTrigger(session, username, beanKey);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param session
	 * @param username
	 * @param beanKey
	 * @throws Exception
	 */
	private void onOpenTrigger(Session session, String username, String beanKey) throws Exception {
		WsOpenListener wsOpenListener = SpringUtils.applicationContext.getBean(beanKey, WsOpenListener.class);
		wsOpenListener.onOpen(username, session);
	}

	/**
	 * @param session
	 * @return
	 */
	private String getClientIp(Session session) {
		// InetSocketAddress addr2 = (InetSocketAddress)getFieldInstance(session.getAsyncRemote(),"base#socketWrapper#socket#sc#remoteAddress");
		SocketChannel socket = (SocketChannel) getFieldInstance(session.getAsyncRemote(),
				"base#socketWrapper#socket#sc");
		String connection = socket.toString();// [connected local=/127.0.0.1:8080 remote=/127.0.0.1:55275]
		String clientIp = connection.substring(connection.lastIndexOf("/") + 1).split(":")[0];
		return clientIp;
	}

	/**
	 * @param session
	 */
	public void updateHeartbeat(Session session) {
		Map<String, Object> userProperties = session.getUserProperties();
		userProperties.put("updateTime", System.currentTimeMillis());
	}

	/**
	 * 
	 * 
	 * onMessage方法是单线程
	 * 
	 * @param username
	 * @param message
	 */
	@OnMessage
	public void onMessage(@PathParam("username") String username, Session session, String message) {
		Threads.execute(() -> {
			try {
				// log.info("Websocket Server recive message -> {}", message);
				JSONObject msg = JSON.parseObject(message);
				String key = WebSocketUtils.getMessageKey(msg);
				Thread thread = (Thread) WebSocketUtils.commands.get(key);
				if (thread != null) {//唤醒阻塞等待结果的线程
					synchronized (thread) {
						if (WebSocketUtils.commands.containsKey(key)) {
							WebSocketUtils.commands.put(key, msg);
							thread.notify();
						}
					}
				} else {// 由具体的处理器处理
					JSONObject commond = msg.getJSONObject("command");
					int iCommand = commond.getInteger("iCommand");

					String simpleName = WebsocketMsgHandler.class.getSimpleName();
					String beanKey = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1) + iCommand;
					if (!SpringUtils.applicationContext.containsBean(beanKey)) {
						beanKey = "defaultMsgHandler";
					}
					WebsocketMsgHandler websocketMsgHandler = SpringUtils.applicationContext.getBean(beanKey,
							WebsocketMsgHandler.class);
					websocketMsgHandler.invoke(msg);
				}

			} catch (Exception e) {
				e.printStackTrace();
			}
		});
	}

	@OnClose
	public void onClose(@PathParam("username") String username, Session session, CloseReason closeReason) {
		synchronized (session.getId().intern()) {
			if (StringUtils.isBlank(username)) {
				username = WebSocketUtils.getUsername(session);
			}
			if (StringUtils.isNotBlank(username)) {
				// 除非能保证自定义的用户名能保证维一性，否则不能使用自定义的用户名来比较Session
				if (WebSocketUtils.clients.remove(username) != null) {
					log.info("{} disconnect Websocket", username);
					log.info("Websocket Client size = {}", WebSocketUtils.clients.size());
				}
				String group = (String) session.getUserProperties().get("group");
				String beanKey = group + WsCloseListener.class.getSimpleName();
				if (SpringUtils.containsBean(beanKey)) {
					WsCloseListener wsCloseListener = SpringUtils.applicationContext.getBean(beanKey, WsCloseListener.class);
					wsCloseListener.onClose(username, session);
				}
			}
		}

	}

	@OnError
	public void onError(Session session, Throwable error) {
		// 能监近websocket的异常情况
		error.printStackTrace();
	}
}